Skip to main content

Transformers

Transformers are pipe functions that modify injected data. Chain them after a provider expression using |.

%provider:path|transformer1(args)|transformer2(args)%
String arguments must use double quotes

Transformer arguments must use " (double quotes), never ' (single quotes). To avoid breaking the YAML string, wrap the entire value in single quotes:

value: '%provider:path|transformer("arg1","arg2")%'

If you must use double quotes for the YAML string, escape the inner quotes with \":

value: "%provider:path|transformer(\"arg1\",\"arg2\")%"
No nested data injection

Data injection does not support nesting %...% inside another %...% expression. Each %...% is resolved independently. If you need to use a dynamic value as an argument, store it first with storeValue and access it in a separate node.


Quick reference

String transformers

TransformerDescription
replaceReplace substrings using regex
trimRemove leading/trailing whitespace
splitSplit a string into an array
sliceExtract a substring by index
toUpperCaseConvert to uppercase
toLowerCaseConvert to lowercase
parseIntParse string to integer
jsonParse (alias: parseJson)Parse a JSON string into an object
parseXmlParse an XML string into an object
encodeURIURL-encode a string
decodeURIURL-decode a string
formatPhoneFormat a phone number
hasWordsCheck if a string contains any of the given words

Array transformers

TransformerDescription
columnExtract a field from each item
joinJoin items into a string
mapRename/remap fields in each item
filterKeep items matching an expression
sortSort items by field(s)
reverseReverse the order of items
flattenFlatten nested arrays

Date transformers

TransformerDescription
formatDateFormat a date value using Luxon
parseDateParse a date string into a Date object

Mixed / universal transformers

TransformerDescription
lengthGet length of a string or array
toStringConvert any value to a string
toJsonSerialize a value to JSON
typeofGet the JavaScript type of a value
getAccess a nested property from an object by path
pickKeep only specified keys from an object
omitRemove specified keys from an object
hbTplRender a Handlebars template

String transformers

formatPhone

Formats a phone number into a specific format.

|formatPhone("format", "country")
ArgumentRequiredDescriptionValues
formatYesOutput format"smart", "e164"
countryYesISO country code"IL", "US", etc.

Examples

# Smart format — local-style with dashes
value: '%chat:phone|formatPhone("smart","IL")%'
# "972521234567" → "052-123-4567"
# E.164 international format
value: '%chat:phone|formatPhone("e164","IL")%'
# "052-123-4567" → "+972521234567"
# Chain with replace to strip dashes
value: '%chat:phone|formatPhone("smart","IL")|replace("-","","g")%'
# "972521234567" → "052-123-4567" → "0521234567"

replace

Replaces occurrences of a substring using a regular expression.

|replace("search", "replacement", "flags")
ArgumentRequiredDescription
searchYesString or regex pattern to find
replacementYesString to replace with
flagsNoRegex flags — "g" for global (all occurrences)

Examples

# Remove all dashes
value: '%chat:phone|formatPhone("smart","IL")|replace("-","","g")%'
# "052-123-4567" → "0521234567"
# Strip protocol from URL
value: '%state:node.get_url.response.signed_url|replace("https://","")%'
# "https://storage.example.com/file.pdf" → "storage.example.com/file.pdf"
# Replace newlines with spaces
value: '%state:node.ask_reason.text|replace("\n"," ","g")%'
# "line one\nline two\nline three" → "line one line two line three"

trim

Removes leading and trailing whitespace.

|trim

Takes no arguments.

Example

value: "%state:node.user_input.text|trim%"
# " hello world " → "hello world"

split

Splits a string into an array by a separator.

|split("separator")
ArgumentRequiredDescription
separatorYesThe delimiter to split on

Examples

# Split comma-separated tags into an array
value: '%state:node.ask_tags.text|split(",")%'
# "red,green,blue" → ["red", "green", "blue"]
# Split by newline
value: '%state:node.ask_address.text|split("\n")%'
# "Street 1\nCity\nCountry" → ["Street 1", "City", "Country"]

slice

Extracts a substring by start and end index (like JavaScript String.slice()).

|slice(start, end)
ArgumentRequiredDefaultDescription
startNo0Start index (0-based). Negative values count from the end.
endNostring lengthEnd index (exclusive). Negative values count from the end.

Examples

# Get first 10 characters
value: "%state:node.long_text.text|slice(0,10)%"
# "0123456789extra" → "0123456789"
# Get last 4 characters (e.g. last 4 digits)
value: "%chat:phone|slice(-4)%"
# "972521234567" → "4567"
# Skip the first 3 characters
value: "%state:store.code|slice(3)%"
# "IL-12345" → "12345"

toUpperCase

Converts a string to uppercase.

|toUpperCase

Takes no arguments.

Example

value: "%state:node.ask_name.text|toUpperCase%"
# "john" → "JOHN"

toLowerCase

Converts a string to lowercase.

|toLowerCase

Takes no arguments.

Example

value: "%state:node.ask_email.text|toLowerCase|trim%"
# " John@Email.COM " → " john@email.com " → "john@email.com"

parseInt

Converts a string to an integer.

|parseInt

Takes no arguments.

Examples

value: "%chat:resolvedUpdateTime|toString|parseInt%"
# "1679500800000" → 1679500800000
# Get timestamp from 5 days ago as integer
value: '%time:now-5d("x")|parseInt%'
# "1710633600000" → 1710633600000

jsonParse

Parses a JSON string into an object. Useful when an API returns JSON as a string field.

Also available as parseJson — identical behavior, either name works.

|jsonParse
|parseJson

Takes no arguments.

Example

value: "%state:store.apiPayload|jsonParse%"
# "{\"name\":\"Alice\",\"age\":30}" → {name: "Alice", age: 30}

value: "%state:store.apiPayload|parseJson%"
# same result

parseXml

Parses an XML string into a JavaScript object.

|parseXml

Takes no arguments.

Example

value: "%state:node.soap_call.response.body|parseXml%"
# "<user><name>Alice</name><age>30</age></user>" → {user: {name: "Alice", age: 30}}

encodeURI

URL-encodes a string. Useful when injecting values into query strings.

|encodeURI

Takes no arguments.

Example

params:
url: "https://api.example.com/search?q=%state:node.ask_query.text|encodeURI%"
# "hello world" → "hello%20world"

decodeURI

URL-decodes a string. Reverses the effect of encodeURI.

|decodeURI

Takes no arguments.

Example

value: "%state:node.get_callback.response.return_url|decodeURI%"
# "hello%20world%3F" → "hello world?"

hasWords

Checks if a string contains at least one of the given words. Returns true or false.

|hasWords(["word1","word2"], caseInsensitive)
ArgumentRequiredDefaultDescription
wordsYesArray of words to search for
caseInsensitiveNofalseSet to true for case-insensitive matching

Examples

# Check if the user's message contains certain keywords (case insensitive)
value: '%state:node.user_msg.text|hasWords(["cancel","ביטול"],true)%'
# "I want to Cancel my order" → true
# Case-sensitive check
value: '%state:node.ask_reason.text|hasWords(["URGENT","ASAP"])%'
# "this is urgent" → false (case-sensitive, "urgent" ≠ "URGENT")
# "this is URGENT" → true
tip

hasWords returns a boolean, making it useful as a value in matchExpression or switch conditions.


Array transformers

column

Extracts a single field from each object in an array, returning a new array of those values.

|column("fieldName")
ArgumentRequiredDescription
fieldNameYesThe field to extract from each item

Examples

# Get the text of all recent incoming messages
value: '%messages:latest(5,-1,"in")|column("text")%'
# [{text: "hi", ...}, {text: "help", ...}] → ["hi", "help"]
# Extract order IDs from API response
value: '%state:node.get_orders.response.orders|column("id")%'
# [{id: "A1", name: "Order 1"}, {id: "A2", name: "Order 2"}] → ["A1", "A2"]
# Get a list of names and join them
value: '%state:store.contacts|column("name")|join(", ")%'
# [{name: "Alice"}, {name: "Bob"}] → ["Alice", "Bob"] → "Alice, Bob"

join

Joins array items into a single string with a separator.

|join("separator")
ArgumentRequiredDescription
separatorYesString to place between items

Examples

# Join with newline
value: '%state:store.items|join("\n")%'
# ["Milk", "Bread", "Eggs"] → "Milk\nBread\nEggs"
# Join with comma and space
value: '%state:node.get_tags.response.tags|column("name")|join(", ")%'
# [{name: "vip"}, {name: "new"}] → ["vip", "new"] → "vip, new"

map

Remaps fields in each object of an array. Creates new objects with renamed keys. Useful for building choice lists from API data.

|map("sourceField::targetField", "sourceField2::targetField2", ...)

Each argument is a "source::target" pair. The source is the field name in the original object, and target is the field name in the output object.

Examples

# Map CRM order data into a choice-compatible list
choices: '%state:node.open_orders.response.orders|map("summary::title","Order_ID::id")%'
# [{summary: "Laptop", Order_ID: "A1"}, {summary: "Phone", Order_ID: "A2"}]
# → [{title: "Laptop", id: "A1"}, {title: "Phone", id: "A2"}]
tip

The ::title mapping is what appears to the user in a choice list. The ::id mapping is the value stored when the user selects it.

# Map services into choices
choices: '%state:node.get_services.response.services|map("service_name::title","service_id::id")%'
# [{service_name: "Haircut", service_id: "5"}, {service_name: "Color", service_id: "8"}]
# → [{title: "Haircut", id: "5"}, {title: "Color", id: "8"}]

filter

Filters an array, keeping only items that match a Filtrex expression. Multiple expressions act as OR conditions (item matches if any expression is true). Non-object items are always excluded.

|filter("expression1", "expression2", ...)
ArgumentRequiredDescription
expressionYes (at least one)A Filtrex expression to match against each item

Supported operators in expressions: ==, !=, >, >=, <, <=, and, or, not, plus boolean fields (truthy check).

Examples

# Keep only active items (truthy check on boolean field)
value: '%state:store.users|filter("active")%'
# [{name: "Alice", active: true}, {name: "Bob", active: false}]
# → [{name: "Alice", active: true}]
# Numeric comparison
value: '%state:node.get_orders.response.orders|filter("score >= 90")%'
# [{id: 1, score: 85}, {id: 2, score: 92}, {id: 3, score: 95}]
# → [{id: 2, score: 92}, {id: 3, score: 95}]
# String comparison
value: '%state:store.employees|filter("department == \"Engineering\"")%'
# [{name: "Alice", department: "Engineering"}, {name: "Bob", department: "Sales"}]
# → [{name: "Alice", department: "Engineering"}]
# Combined conditions with AND
value: '%state:store.users|filter("age > 27 and active")%'
# [{name: "Alice", age: 25, active: true}, {name: "Bob", age: 30, active: false}, {name: "Charlie", age: 35, active: true}]
# → [{name: "Charlie", age: 35, active: true}]
# Multiple expressions (OR logic) — matches Engineering OR age >= 35
value: '%state:store.employees|filter("department == \"Engineering\"","age >= 35")%'
# [{name: "Alice", age: 25, department: "Engineering"}, {name: "Bob", age: 30, department: "Sales"}, {name: "Charlie", age: 35, department: "Marketing"}]
# → [{name: "Alice", age: 25, department: "Engineering"}, {name: "Charlie", age: 35, department: "Marketing"}]

sort

Sorts an array of objects by one or more fields (ascending). Uses lodash sortBy under the hood.

|sort("field1", "field2", ...)
ArgumentRequiredDescription
fieldYes (at least one)Field name to sort by. Supports dot-notation for nested fields.

When multiple fields are given, items are sorted by the first field, then ties are broken by the second field, and so on.

Examples

# Sort orders by date
value: '%state:node.get_orders.response.orders|sort("created_at")%'
# [{id: 3, created_at: "2025-03-01"}, {id: 1, created_at: "2025-01-15"}, {id: 2, created_at: "2025-02-10"}]
# → [{id: 1, created_at: "2025-01-15"}, {id: 2, created_at: "2025-02-10"}, {id: 3, created_at: "2025-03-01"}]
# Sort by department, then by name within each department
value: '%state:store.employees|sort("department","name")%'
# [{department: "Sales", name: "Zoe"}, {department: "Engineering", name: "Charlie"}, {department: "Engineering", name: "Alice"}]
# → [{department: "Engineering", name: "Alice"}, {department: "Engineering", name: "Charlie"}, {department: "Sales", name: "Zoe"}]
tip

sort always sorts ascending. To get descending order, chain with reverse:

value: '%state:store.orders|sort("created_at")|reverse%'
# (oldest → newest) → (newest → oldest)

reverse

Reverses the order of items in an array.

|reverse

Takes no arguments.

Examples

# Get messages oldest-first (reverse the default newest-first)
value: "%messages:latest(10)|reverse%"
# [newest, ..., oldest] → [oldest, ..., newest]
# Sort descending by combining sort + reverse
value: '%state:store.scores|sort("value")|reverse%'
# [{value: 50}, {value: 80}, {value: 30}] → [{value: 30}, {value: 50}, {value: 80}] → [{value: 80}, {value: 50}, {value: 30}]

flatten

Flattens nested arrays to a specified depth.

|flatten(depth)
ArgumentRequiredDefaultDescription
depthNo1How many levels deep to flatten

Examples

# Flatten one level (default)
value: "%state:store.nestedItems|flatten%"
# [[1, 2], [3, 4], [5]] → [1, 2, 3, 4, 5]
# Flatten deeply nested arrays
value: "%state:store.deeplyNested|flatten(3)%"
# [[[1, 2]], [[3, [4]]]] → [1, 2, 3, 4]

Date transformers

formatDate

Formats a date value (string, number, or Date) into a formatted string using Luxon tokens.

|formatDate("format", "timezone")
ArgumentRequiredDefaultDescription
formatNo"yyyy-MM-dd"Luxon format string, or "iso" for ISO 8601
timezoneNo"UTC"Timezone — IANA name, "ist" (alias for Asia/Jerusalem), or "UTC"

Examples

# Format a timestamp from an API response
value: '%state:node.get_appointment.response.date|formatDate("dd/MM/yyyy","ist")%'
# "2025-06-15T10:30:00Z" → "15/06/2025"
# ISO format
value: '%state:store.createdAt|formatDate("iso")%'
# 1718451000000 → "2025-06-15T10:30:00.000Z"
# Full date and time in Israel timezone
value: '%state:node.get_event.response.start|formatDate("dd.MM.yyyy HH:mm","ist")%'
# "2025-06-15T10:30:00Z" → "15.06.2025 13:30"
# Just the time
value: '%state:store.scheduledTime|formatDate("HH:mm","ist")%'
# "2025-06-15T10:30:00Z" → "13:30"
info

This transformer is different from the time provider. Use the time provider to get the current time with arithmetic (e.g. %time:now-5d%). Use formatDate to reformat an existing date/timestamp value from state or an API response.


parseDate

Parses a date string into a Date object, using a specified format and timezone. Useful when you receive dates in a non-standard format and need to convert them.

|parseDate("format", "timezone")
ArgumentRequiredDefaultDescription
formatNo"yyyy-MM-dd"Luxon format string to parse with, or "iso" for ISO 8601
timezoneNo"utc"Timezone to interpret the input in — IANA name, "ist", or "utc"

Examples

# Parse a date string in dd/MM/yyyy format
value: '%state:node.ask_date.text|parseDate("dd/MM/yyyy","ist")%'
# "15/06/2025" → Date(2025-06-14T21:00:00.000Z)
# Parse an ISO date string
value: '%state:store.dateString|parseDate("iso")%'
# "2025-06-15T10:30:00Z" → Date(2025-06-15T10:30:00.000Z)
# Parse and then reformat
value: '%state:node.ask_date.text|parseDate("dd/MM/yyyy","ist")|formatDate("yyyy-MM-dd","UTC")%'
# "15/06/2025" → Date → "2025-06-14"

Mixed / universal transformers

length

Returns the length of a string or array.

|length

Takes no arguments. Works on both strings and arrays.

Examples

# Check the length of user input (use in matchExpression)
check_id_length:
type: func
func_type: system
func_id: matchExpression
params:
expression: 'length == 9'
length: "%state:node.ask_id.text|length%"
on_complete: valid_id
on_failure: invalid_id
# "123456789" → 9 (matches expression)
# "12345" → 5 (fails expression)
# Get number of items in an array
value: "%state:node.get_orders.response.orders|length%"
# [{...}, {...}, {...}] → 3
# Check message length
value: "%state:node.ask_text.text|length%"
# "Hello" → 5

toString

Converts any value to its string representation.

|toString

Takes no arguments.

Examples

value: "%state:store.lastOutMsg.0.timestamp|toString|parseInt%"
# 1679500800000 → "1679500800000" → 1679500800000
value: "%chat:resolvedUpdateTime|toString%"
# 1679500800000 → "1679500800000"

toJson

Serializes any value to a JSON string.

|toJson(prettyPrint)
ArgumentRequiredDefaultDescription
prettyPrintNofalseSet to true for indented (pretty-printed) output

Examples

# Serialize an object to a compact JSON string (e.g. for a webhook body)
value: "%state:store.userData|toJson%"
# {name: "Alice", age: 30} → '{"name":"Alice","age":30}'
# Pretty-print for a readable log or message
value: "%state:node.api_call.response|toJson(true)%"
# {name: "Alice", age: 30} → '{
# "name": "Alice",
# "age": 30
# }'

typeof

Returns the JavaScript type of the value ("string", "number", "boolean", "object", "undefined", etc.).

|typeof

Takes no arguments.

Examples

value: "%state:store.apiResult|typeof%"
# {name: "Alice"} → "object"
# "hello" → "string"
# 42 → "number"
# true → "boolean"

get

Accesses a nested property from an object using dot notation or separate path segments (powered by lodash.get) - Commonly used for when the property name is in Hebrew which breaks regular data injection (can't use %chat:crmData.סטטוס% for example). Returns undefined if the path does not exist.

|get("path")
ArgumentRequiredDescription
pathYes (at least one)Property path string. Use dot notation for nested access (e.g. "user.name") or pass multiple arguments for each segment.

Examples

Useful Use Case

get transformer is useful when you need to access a nested property from a json-string that is returned from an API call.

value: '%state:node.api_call.response|jsonParse|get("user.profile.email")|toUpperCase%'
# "{\"user\": {\"profile\": {\"email\": \"a@b.com\"}}}" → "a@b.com" → "A@B.COM"
# Access a top-level crmData field, even in Hebrew
value: '%chat:crmData|get("סטטוס")%'
# {סטטוס: "active", name: "Alice"} → "active"
# Access a nested field using dot notation
value: '%state:node.api_call.response|get("user.profile.email")%'
# {user: {profile: {email: "a@b.com"}}} → "a@b.com"
# Use in switchNode to route on a CRM field value
route_by_type:
type: func
func_type: system
func_id: switchNode
params:
input: '%chat:crmData|get("type")%'
cases:
"vip": vip_flow
"regular": regular_flow
empty: unknown_flow
on_complete: unknown_flow
# Access an array element by index
value: '%state:store.items|get("0.name")%'
# [{name: "First"}, {name: "Second"}] → "First"

pick

Keeps only the specified keys from an object, discarding everything else (powered by lodash.pick).

|pick("key1","key2",...)
ArgumentRequiredDescription
keyYes (at least one)Key name to keep. Pass multiple arguments to keep multiple keys.

Examples

# Keep only name and email from a CRM response
value: '%state:node.get_contact.response|pick("name","email")%'
# {name: "Alice", email: "a@b.com", internal_id: 99, score: 5} → {name: "Alice", email: "a@b.com"}
# Trim a crmData object before sending to a webhook
value: '%chat:crmData|pick("id","name","phone")%'
# {id: "123", name: "Alice", phone: "052...", deepLink: "...", status: "2"} → {id: "123", name: "Alice", phone: "052..."}

omit

Removes specified keys from an object, keeping everything else (powered by lodash.omit).

|omit("key1","key2",...)
ArgumentRequiredDescription
keyYes (at least one)Key name to remove. Pass multiple arguments to remove multiple keys.

Examples

# Remove internal fields before sending to a webhook
value: '%chat:crmData|omit("deepLink","status")%'
# {id: "123", name: "Alice", deepLink: "https://...", status: "2"} → {id: "123", name: "Alice"}
# Strip auth fields from an API response before storing
value: '%state:node.login.response|omit("token","refresh_token")%'
# {userId: "5", name: "Alice", token: "abc", refresh_token: "xyz"} → {userId: "5", name: "Alice"}

hbTpl

Renders a Handlebars template against the input data. The input must be an object or array.

|hbTpl("template", escape, skipNonObject)
ArgumentRequiredDefaultDescription
templateYesHandlebars template string
escapeNofalseIf true, HTML-escapes output. Default (false) means no escaping.
skipNonObjectNotrueIf true (default), returns empty string for non-object inputs instead of throwing an error.

Built-in helpers

{\{#each .}} — Iterate

Standard Handlebars iteration over arrays:

{{#each .}}
{{this.name}}: {{this.value}}
{{/each}}

{{date source format timezone}} — Format dates

Formats a date/timestamp inside the template.

ArgumentRequiredDefaultDescription
sourceYesTimestamp (string, number, or Date)
formatNo"d.L.y, HH:mm:ss"Luxon format string
timezoneNoCustomer default timezoneIANA timezone or the customer's configured default
{{date timestamp}}
{{date timestamp "dd/MM/yyyy"}}
{{date timestamp "HH:mm" "Asia/Jerusalem"}}

{\{#when left 'op' right}} — Conditional

Compares two values using an operator.

OperatorDescription
eqEqual (===)
noteqNot equal (!==)
gtGreater than (numbers only)
gteqGreater than or equal (numbers only)
ltLess than (numbers only)
lteqLess than or equal (numbers only)
orLogical OR
andLogical AND
%Modulo equals zero (numbers only)

Supports {{else}} blocks:

{{#when direction 'eq' 'incoming'}}Customer{{else}}Agent{{/when}}

{{provide 'provider' 'key'}} — Access other providers

Fetches data from any data injection provider inside the template:

{{provide 'chat' 'title'}}
{{provide 'chat' 'phone'}}

{{log message data}} — Debug logging

Logs to the server console (for debugging). Supports an optional severity hash parameter.

{{log "Processing item" this severity="debug"}}

Full examples

# Format a conversation log from recent messages
value: |
%messages:latest(5,1,"any","text")|hbTpl("
{{#each .}}
[{{date timestamp}}] {{#when direction 'eq' 'incoming'}}{{provide 'chat' 'title'}}{{else}}{{agent}}{{/when}}:
{{text}}

{{/each}}
")%
# Input: [{direction: "incoming", text: "Hi", timestamp: 1718451000000, ...}, {direction: "outgoing", text: "Hello!", agent: "Support", timestamp: 1718451060000, ...}]
# Output:
# [15.6.2025, 13:30:00] John:
# Hi
#
# [15.6.2025, 13:31:00] Support:
# Hello!
# Build a summary of order items
value: |
%state:node.get_order.response.items|hbTpl("
{{#each .}}
• {{name}} x{{quantity}} — ₪{{price}}
{{/each}}
")%
# Input: [{name: "Laptop", quantity: 1, price: 3500}, {name: "Mouse", quantity: 2, price: 80}]
# Output:
# • Laptop x1 — ₪3500
# • Mouse x2 — ₪80
# Conditional content based on status
value: |
%state:node.check_status.response|hbTpl("
{{#when status 'eq' 'approved'}}Your request was approved!{{else}}Your request is still pending.{{/when}}
")%
# Input: {status: "approved"} → "Your request was approved!"
# Input: {status: "pending"} → "Your request is still pending."

Chaining transformers

You can chain multiple transformers left to right. Each transformer receives the output of the previous one:

# Format phone → remove dashes
value: '%chat:phone|formatPhone("smart","IL")|replace("-","","g")%'
# "972521234567" → "052-123-4567" → "0521234567"
# Convert to string → parse as integer
value: "%chat:resolvedUpdateTime|toString|parseInt%"
# 1679500800000 → "1679500800000" → 1679500800000
# Get timestamp from 5 days ago as integer
value: '%time:now-5d("x")|parseInt%'
# "1710633600000" → 1710633600000
# Lowercase and trim user input
value: "%state:node.ask_email.text|toLowerCase|trim%"
# " John@Email.COM " → " john@email.com " → "john@email.com"
# Sort orders by date descending, extract titles, join
value: '%state:store.orders|sort("date")|reverse|column("title")|join("\n")%'
# [{title: "B", date: "2025-01"}, {title: "A", date: "2025-03"}, {title: "C", date: "2025-02"}]
# → sort → [{title: "B",...}, {title: "C",...}, {title: "A",...}]
# → reverse → [{title: "A",...}, {title: "C",...}, {title: "B",...}]
# → column → ["A", "C", "B"]
# → join → "A\nC\nB"
# Parse a date from user input, then reformat it for an API
value: '%state:node.ask_date.text|parseDate("dd/MM/yyyy","ist")|formatDate("yyyy-MM-dd","UTC")%'
# "15/06/2025" → Date → "2025-06-14"
# Filter active users, extract names, join as comma-separated list
value: '%state:store.users|filter("active")|column("name")|join(", ")%'
# [{name: "Alice", active: true}, {name: "Bob", active: false}, {name: "Charlie", active: true}]
# → [{name: "Alice", active: true}, {name: "Charlie", active: true}]
# → ["Alice", "Charlie"]
# → "Alice, Charlie"